// 
// Entify project
//  
// Author:
//       Lauri Taimila <lauri@taimila.com>
// 
// Copyright (c) 2010 Lauri Taimila
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Linq;
using System.Collections.Generic;

namespace MonoDevelop.EntifyEditor
{
	/// <summary>
	/// Entify editor root widget. Implements visual designer.
	/// </summary>
	public partial class EntifyEditorWidget : Gtk.Bin
	{
		/// <summary>
		/// Event indicates that entity set model has been changed from editor.
		/// </summary>
		public delegate void EntitySetChanged();
    	public event EntitySetChanged ModelChanged;

		/// <summary>
		/// Entity set model that is modified by editor.
		/// </summary>
		private EntitySet model;

		/// <summary>
		/// Models for UI components.
		/// </summary>
		private Gtk.ListStore entityListStore;
		private Gtk.ListStore propertyListStore;
		private Gtk.ListStore relationListStore;

		/// <summary>
		/// Sub widgets of the entify editor.
		/// </summary>
		private EntityDetailsWidget entityDetails;
		private RelationDetailsWidget relationDetails;
		private PropertyDetailsWidget propertyDetails;
		private EntitySetCanvasWidget entitySetCanvas;

		/// <summary>
		/// Gets entity set model from widget.
		/// </summary>
		public EntitySet EntitySet
		{
			get
			{
				return this.model;
			}
		}

		/// <summary>
		/// Create new EntifyEditorWidget instance.
		/// </summary>
		public EntifyEditorWidget(EntitySet model)
		{
			this.model = model;

			this.Build();
			this.BuildEntityList();
			this.BuildPropertyList();
			this.BuildRelationList();
			this.BuildDetailViews();
			this.BuildEntityCanvas();

			this.EditorContainer.HeightRequest = 110;

			//TODO: Refactor to separated method
			this.entitySetCanvas.ActiveEntityChanged += delegate
			{
				if(this.EntitySet.ActiveEntity == null)
				{
					this.EntityList.Selection.UnselectAll();
				}

				Gtk.TreeIter iter;
				this.entityListStore.GetIterFirst(out iter);
				bool notFound = true;
				while(notFound)
				{
					if(this.EntitySet.ActiveEntity == (this.entityListStore.GetValue(iter, 0) as Entity))
					{
						this.EntityList.Selection.SelectIter(iter);
						notFound = false;
					}
					else
					{
						if(!this.entityListStore.IterNext(ref iter))
						{
							notFound = false;
						}
					}
				}

				this.OnEntitySelectionChanged();
			};

			this.ModelChanged += delegate {
				this.entitySetCanvas.QueueDraw();
			};

			this.entityDetails.DetailsChanged += delegate {
				this.EntityList.QueueDraw();
				this.relationDetails.RefreshRelationTypeCombobox();
				this.ModelChanged();
			};

			this.propertyDetails.DetailsChanged += delegate {
				this.PropertyList.QueueDraw();
				this.ModelChanged();
			};

			this.relationDetails.DetailsChanged += delegate {
				this.RelationList.QueueDraw();
				this.ModelChanged();
			};

			this.NamespaceEntry.Text = this.model.Namespace ?? string.Empty;
			this.PartialEntityClassesCheckbox.Active = this.model.GeneratePartialClasses;

			this.ShowAll();
		}

		private void BuildEntityList()
		{
			this.entityListStore = new Gtk.ListStore(typeof(Entity));

			foreach(Entity entity in this.model.Entities)
			{
				this.entityListStore.AppendValues(entity);
			}

			Gtk.TreeViewColumn entityNameColumn = new Gtk.TreeViewColumn();
			entityNameColumn.Title = "Entity";
			Gtk.CellRendererText entityNameCell = new Gtk.CellRendererText();
			entityNameColumn.PackStart (entityNameCell, true);
			entityNameColumn.SetCellDataFunc (entityNameCell, new Gtk.TreeCellDataFunc (this.RenderEntityName));

			this.EntityList.AppendColumn(entityNameColumn);
			this.EntityList.Selection.Mode = Gtk.SelectionMode.Single;
			this.EntityList.Model = this.entityListStore;
		}

		private void BuildPropertyList()
		{
			this.propertyListStore = new Gtk.ListStore(typeof(Property));

			Gtk.TreeViewColumn propertyNameColumn = new Gtk.TreeViewColumn();
			propertyNameColumn.Title = "Property";
			Gtk.CellRendererText propertyNameCell = new Gtk.CellRendererText();
			propertyNameColumn.PackStart (propertyNameCell, true);
			propertyNameColumn.SetCellDataFunc (propertyNameCell, new Gtk.TreeCellDataFunc (this.RenderPropertyName));

			this.PropertyList.AppendColumn(propertyNameColumn);
			this.PropertyList.Model = this.propertyListStore;
			this.PropertyList.Selection.Mode = Gtk.SelectionMode.Single;
		}

		private void BuildRelationList()
		{
			this.relationListStore = new Gtk.ListStore(typeof(Relation));

			Gtk.TreeViewColumn relationNameColumn = new Gtk.TreeViewColumn();
			relationNameColumn.Title = "Relation";
			Gtk.CellRendererText relationNameCell = new Gtk.CellRendererText();
			relationNameColumn.PackStart (relationNameCell, true);
			relationNameColumn.SetCellDataFunc (relationNameCell, new Gtk.TreeCellDataFunc (this.RenderRelationName));

			this.RelationList.AppendColumn(relationNameColumn);
			this.RelationList.Model = this.relationListStore;
			this.RelationList.Selection.Mode = Gtk.SelectionMode.Single;
		}

		private void BuildDetailViews()
		{
			this.entityDetails = new EntityDetailsWidget();
			this.relationDetails = new RelationDetailsWidget();
			this.propertyDetails = new PropertyDetailsWidget();

			foreach(Entity entity in this.model.Entities)
			{
				this.relationDetails.AddEntityToRelationTypeCombobox(entity);
			}

			this.EntitiesContainer.Add(this.entityDetails);
			this.RelationsContainer.Add(this.relationDetails);
			this.PropertiesContainer.Add(this.propertyDetails);
		}

		private void BuildEntityCanvas()
		{
			this.entitySetCanvas = new EntitySetCanvasWidget(this.model);
			this.VisualAreaScroller.Add(this.entitySetCanvas);
		}

		/// <summary>
		/// Called when namespace has been updated by user. Updates model accordingly.
		/// </summary>
		protected virtual void OnNamespaceEntryChanged (object sender, System.EventArgs e)
		{
			if(this.model != null)
			{
				this.model.Namespace = this.NamespaceEntry.Text.Trim();
				this.ModelChanged();
			}
		}

		/// <summary>
		/// Called when pertial entity class generation checkbox has been toggled. Updates model accordingly.
		/// </summary>
		protected virtual void OnPartialEntityClassesCheckboxToggled (object sender, System.EventArgs e)
		{
			if(this.model != null)
			{
				this.model.GeneratePartialClasses = this.PartialEntityClassesCheckbox.Active;
				this.ModelChanged();
			}
		}

		/// <summary>
		/// Called when "Add entity" button is clicked. Adds new entity to entity set model
		/// and updates entity list UI component to display it.
		/// </summary>
		protected virtual void OnAddEntityButtonClicked (object sender, System.EventArgs e)
		{
			string name = this.GetNewEntityName();
			Entity newEntity = new Entity(name);
			this.model.Entities.Add(newEntity);
			this.entityListStore.AppendValues(newEntity);

			// Select last item in the list (the one we just created)
			Gtk.TreeIter iter;
			this.entityListStore.GetIterFirst(out iter);
			bool notFound = true;
			while(notFound)
			{
				if(name == (this.entityListStore.GetValue(iter, 0) as Entity).Name)
				{
					this.EntityList.Selection.SelectIter(iter);
					notFound = false;
				}
				else
				{
					this.entityListStore.IterNext(ref iter);
				}
			}

			this.RemoveEntityButton.Sensitive = true;
			this.AddPropertyButton.Sensitive = true;
			this.AddRelationButton.Sensitive = true;

			this.relationDetails.AddEntityToRelationTypeCombobox(newEntity);

			this.model.ActiveEntity = this.GetSelectedEntity();
			this.UpdateEntityDetailsView();
			this.UpdatePropertyList();
			this.UpdateRelationList();
			this.ModelChanged();
			this.entityDetails.FocusToEntityNameEntry();
		}

		/// <summary>
		/// Called when "Add property" button is clicked. Adds new property to entity set model
		/// and updates property list UI component to display it.
		/// </summary>
		protected virtual void OnAddPropertyButtonClicked (object sender, System.EventArgs e)
		{
			string name = this.GetNewPropertyName();
			Property newProperty = new Property(name);
			this.GetSelectedEntity().Properties.Add(newProperty);
			this.propertyListStore.AppendValues(newProperty);

			// Select last item in the list (the one we just created)
			Gtk.TreeIter iter;
			this.propertyListStore.GetIterFirst(out iter);
			bool notFound = true;

			while(notFound)
			{
				if(name == (this.propertyListStore.GetValue(iter, 0) as Property).Name)
				{
					this.PropertyList.Selection.SelectIter(iter);
					notFound = false;
				}
				else
				{
					this.propertyListStore.IterNext(ref iter);
				}
			}

			this.RemovePropertyButton.Sensitive = true;
			this.UpdatePropertyDetailsView();
			this.ModelChanged();
			this.propertyDetails.FocusToPropertyNameEntry();
		}

		/// <summary>
		/// Called when "Add relation" button is clicked. Adds new relation to entity set model
		/// and updates relation list UI component to display it.
		/// </summary>
		protected virtual void OnAddRelationButtonClicked (object sender, System.EventArgs e)
		{
			string name = this.GetNewRelationName();
			Relation newRelation = new Relation(name);
			this.GetSelectedEntity().Relations.Add(newRelation);
			this.relationListStore.AppendValues(newRelation);

			// Select last item in the list (the one we just created)
			Gtk.TreeIter iter;
			this.relationListStore.GetIterFirst(out iter);
			bool notFound = true;
			while(notFound)
			{
				if(name == (this.relationListStore.GetValue(iter, 0) as Relation).Name)
				{
					this.RelationList.Selection.SelectIter(iter);
					notFound = false;
				}
				else
				{
					this.relationListStore.IterNext(ref iter);
				}
			}

			this.RemoveRelationButton.Sensitive = true;
			this.UpdateRelationDetailsView();
			this.ModelChanged();
			this.relationDetails.FocusToRelationNameEntry();
		}

		/// <summary>
		/// Removes currently selected entity from entity set model and updates entity list UI.
		/// </summary>
		protected virtual void OnRemoveEntityButtonClicked (object sender, System.EventArgs e)
		{
			// Get iterator for current entity selection
			Gtk.TreeIter treeIterator;
			this.EntityList.Selection.GetSelected(out treeIterator);
			Entity entity = this.entityListStore.GetValue(treeIterator, 0) as Entity;

			// Remove entity from model, entity list and relation type list
			this.model.Entities.Remove(entity);
			this.entityListStore.Remove(ref treeIterator);
			this.relationDetails.RemoveEntityFromRelationTypeCombobox(entity);

			// Update views
			this.RemoveEntityButton.Sensitive = false;
			this.UpdateEntityDetailsView();
			this.UpdatePropertyList();
			this.UpdateRelationList();

			this.ModelChanged();
		}

		/// <summary>
		/// Removes currently selected property from entity set model and updates property list UI.
		/// </summary>
		protected virtual void OnRemovePropertyButtonClicked (object sender, System.EventArgs e)
		{
			Entity entity = this.GetSelectedEntity();
			Property property = this.GetSelectedProperty();

			// Remove from model and list
			Gtk.TreeIter treeIterator;
			this.PropertyList.Selection.GetSelected(out treeIterator);
			this.propertyListStore.Remove(ref treeIterator);
			entity.Properties.Remove(property);

			// Update view
			this.RemovePropertyButton.Sensitive = false;
			this.UpdatePropertyDetailsView();

			this.ModelChanged();
		}

		/// <summary>
		/// Removes currently selected relation from entity set model and updates relation list UI.
		/// </summary>
		protected virtual void OnRemoveRelationButtonClicked (object sender, System.EventArgs e)
		{
			Entity entity = this.GetSelectedEntity();
			Relation relation = this.GetSelectedRelation();

			// Remove from model and list
			Gtk.TreeIter treeIterator;
			this.RelationList.Selection.GetSelected(out treeIterator);
			this.relationListStore.Remove(ref treeIterator);
			entity.Relations.Remove(relation);

			// Update view
			this.RemoveRelationButton.Sensitive = false;
			this.UpdateRelationDetailsView();

			this.ModelChanged();
		}

		/// <summary>
		/// Changes the currently selected entity, therefore all views must be updated to display
		/// data of the new selection.
		/// </summary>
		protected virtual void OnEntityListCursorChanged (object sender, System.EventArgs e)
		{
			this.EntitySet.ActiveEntity = this.GetSelectedEntity();
			this.OnEntitySelectionChanged();
		}

		/// <summary>
		/// Called when new property is selected. Updates property details view accordingly.
		/// </summary>
		protected virtual void OnPropertyListCursorChanged (object sender, System.EventArgs e)
		{
			this.RemovePropertyButton.Sensitive = true;
			this.UpdatePropertyDetailsView();
		}

		/// <summary>
		/// Called when relation selection changes.
		/// </summary>
		protected virtual void OnRelationListCursorChanged (object sender, System.EventArgs e)
		{
			this.RemoveRelationButton.Sensitive = true;
			this.UpdateRelationDetailsView();
		}

		/// <summary>
		/// Updates view to correspond selected entity.
		/// </summary>
		private void OnEntitySelectionChanged()
		{
			if(this.EntitySet.ActiveEntity == null)
			{
				this.RemoveEntityButton.Sensitive = false;
				this.AddPropertyButton.Sensitive = false;
				this.AddRelationButton.Sensitive = false;
			}
			else
			{
				this.RemoveEntityButton.Sensitive = true;
				this.AddPropertyButton.Sensitive = true;
				this.AddRelationButton.Sensitive = true;
			}

			this.UpdateEntityDetailsView();
			this.UpdatePropertyList();
			this.UpdateRelationList();
		}

		/// <summary>
		/// Updates property list view to display properties of the
		/// currently selected entity.
		/// </summary>
		private void UpdatePropertyList()
		{
			this.propertyListStore.Clear();
			Entity entity = this.GetSelectedEntity();

			if(entity == null)
			{
				this.AddPropertyButton.Sensitive = false;
				this.RemovePropertyButton.Sensitive = false;
			}
			else if(entity.Properties.Count == 0)
			{
				this.AddPropertyButton.Sensitive = true;
				this.RemovePropertyButton.Sensitive = false;
			}
			else
			{
				foreach(Property p in entity.Properties)
				{
					this.propertyListStore.AppendValues(p);
				}

				Gtk.TreeIter propertyIter;
				this.propertyListStore.GetIterFirst(out propertyIter);
				this.PropertyList.Selection.SelectIter(propertyIter);
				this.RemovePropertyButton.Sensitive = true;
			}

			this.UpdatePropertyDetailsView();
		}

		/// <summary>
		/// Updates relation list view to display relations of the
		/// currently selected entity.
		/// </summary>
		private void UpdateRelationList()
		{
			this.relationListStore.Clear();
			Entity entity = this.GetSelectedEntity();

			if(entity == null)
			{
				this.AddRelationButton.Sensitive = false;
				this.RemoveRelationButton.Sensitive = false;
			}
			else if(entity.Relations.Count == 0)
			{
				this.AddRelationButton.Sensitive = true;
				this.RemoveRelationButton.Sensitive = false;
			}
			else
			{
				foreach(Relation p in entity.Relations)
				{
					this.relationListStore.AppendValues(p);
				}

				Gtk.TreeIter relationIter;
				this.relationListStore.GetIterFirst(out relationIter);
				this.RelationList.Selection.SelectIter(relationIter);
				this.RemoveRelationButton.Sensitive = true;
			}

			this.UpdateRelationDetailsView();
		}

		/// <summary>
		/// Updates entity details view to contain currently selected entity's
		/// information or clears the details view if no selection is made.
		/// </summary>
		private void UpdateEntityDetailsView()
		{
			this.entityDetails.Model = this.GetSelectedEntity();
		}

		/// <summary>
		/// Updates property details view to contain currently selected property's
		/// information or clears the details view if no selection is made.
		/// </summary>
		private void UpdatePropertyDetailsView()
		{
			this.propertyDetails.Model = this.GetSelectedProperty();
		}

		/// <summary>
		/// Updates relation details view to contain currently selected relation
		/// information or clears the details view if no selection is made.
		/// </summary>
		private void UpdateRelationDetailsView()
		{
			this.relationDetails.Model = this.GetSelectedRelation();
		}

		/// <summary>
		/// Get currently selected entity. Returns null if no entity is selected.
		/// </summary>
		/// <returns>
		/// Currently selected entity or null. <see cref="Entity"/>
		/// </returns>
		private Entity GetSelectedEntity()
		{
			Entity entity = null;

			try
			{
				Gtk.TreeIter treeIterator;
				this.EntityList.Selection.GetSelected(out treeIterator);
				entity = this.entityListStore.GetValue(treeIterator, 0) as Entity;
			}
			catch { }

			return entity;
		}

		/// <summary>
		/// Get currently selected property. Returns null if no property is selected.
		/// </summary>
		/// <returns>
		/// Currently selected property or null. <see cref="Entity"/>
		/// </returns>
		private Property GetSelectedProperty()
		{
			Entity entity = this.GetSelectedEntity();

			if(entity == null)
			{
				return null;
			}

			Property property = null;

			try
			{
				Gtk.TreeIter treeIterator;
				this.PropertyList.Selection.GetSelected(out treeIterator);
				property = this.propertyListStore.GetValue(treeIterator, 0) as Property;
			}
			catch { }

			return property;
		}

		/// <summary>
		/// Get currently selected relation. Returns null if no relation is selected.
		/// </summary>
		/// <returns>
		/// Currently selected relation or null. <see cref="Entity"/>
		/// </returns>
		private Relation GetSelectedRelation()
		{
			Entity entity = this.GetSelectedEntity();

			if(entity == null)
			{
				return null;
			}

			Relation relation = null;

			try
			{
				Gtk.TreeIter treeIterator;
				this.RelationList.Selection.GetSelected(out treeIterator);
				relation = this.relationListStore.GetValue(treeIterator, 0) as Relation;
			}
			catch { }

			return relation;
		}

		/// <summary>
		/// Gets name for new entity. Name is unique in current entity set.
		/// </summary>
		/// <returns>
		/// Name for new entity. <see cref="System.String"/>
		/// </returns>
		private string GetNewEntityName()
		{
			string name = "Entity";
			string finalName = "";
			int counter = 1;
			bool alreadyExists = true;

			while(alreadyExists)
			{
				finalName = name + counter;

				if(!this.model.Entities.Select(p => p.Name).Contains(finalName))
				{
					alreadyExists = false;
				}

				counter++;
			}

			return finalName;
		}

		/// <summary>
		/// Gets name for new property. Name is unique in current entity.
		/// </summary>
		/// <returns>
		/// Name for new property. <see cref="System.String"/>
		/// </returns>
		private string GetNewPropertyName()
		{
			Entity entity = this.GetSelectedEntity();
			string name = "Property";
			string finalName = "";
			int counter = 1;
			bool alreadyExists = true;

			while(alreadyExists)
			{
				finalName = name + counter;

				if(!entity.Properties.Select(p => p.Name).Contains(finalName))
				{
					alreadyExists = false;
				}

				counter++;
			}

			return finalName;
		}

		/// <summary>
		/// Gets name for new realtion. Name is unique in current entity.
		/// </summary>
		/// <returns>
		/// Name for new relation. <see cref="System.String"/>
		/// </returns>
		private string GetNewRelationName()
		{
			Entity entity = this.GetSelectedEntity();
			string name = "Relation";
			string finalName = "";
			int counter = 1;
			bool alreadyExists = true;

			while(alreadyExists)
			{
				finalName = name + counter;

				if(!entity.Relations.Select(p => p.Name).Contains(finalName))
				{
					alreadyExists = false;
				}

				counter++;
			}

			return finalName;
		}

		/// <summary>
		/// Rederer for entity list name column.
		/// </summary>
		private void RenderEntityName (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			Entity entity = (Entity) this.entityListStore.GetValue(iter, 0);
			(cell as Gtk.CellRendererText).Text = entity.Name;
		}

		/// <summary>
		/// Rederer for property list name column.
		/// </summary>
		private void RenderPropertyName (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			Property property = (Property) this.propertyListStore.GetValue(iter, 0);
			(cell as Gtk.CellRendererText).Text = property.Name;
		}

		/// <summary>
		/// Rederer for relation list name column.
		/// </summary>
		private void RenderRelationName (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			Relation relation = (Relation) this.relationListStore.GetValue(iter, 0);
			(cell as Gtk.CellRendererText).Text = relation.Name;
		}
	}
}
